Coverage Report

Created: 2026-02-05 09:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
D:\a\scloud-dns\scloud-dns\src\dns\packet\mod.rs
Line
Count
Source
1
use crate::dns::packet::additional::AdditionalSection;
2
use crate::dns::packet::answer::AnswerSection;
3
use crate::dns::packet::authority::AuthoritySection;
4
use crate::dns::packet::header::Header;
5
use crate::dns::packet::question::QuestionSection;
6
use crate::exceptions::SCloudException;
7
use rand::random;
8
9
pub(crate) mod additional;
10
pub(crate) mod answer;
11
pub(crate) mod authority;
12
pub(crate) mod header;
13
pub(crate) mod question;
14
15
#[derive(Debug, PartialEq)]
16
pub struct DNSPacket {
17
    pub header: Header,
18
    pub questions: Vec<QuestionSection>,
19
    pub answers: Vec<AnswerSection>,
20
    pub authorities: Vec<AuthoritySection>,
21
    pub additionals: Vec<AdditionalSection>,
22
}
23
24
impl DNSPacket {
25
    /// Deserialize the DNS packet from a byte array
26
    /// # Exemple :
27
    /// ```
28
    /// // Raw DNS query for `github.com` with type A
29
    /// let raw_packet: Vec<u8> = vec![
30
    ///     0x12, 0x34, // ID
31
    ///     0x01, 0x00, // Flags (standard query, RD = 1)
32
    ///     0x00, 0x01, // QDCOUNT
33
    ///     0x00, 0x00, // ANCOUNT
34
    ///     0x00, 0x00, // NSCOUNT
35
    ///     0x00, 0x00, // ARCOUNT
36
    ///     0x06, b'g', b'i', b't', b'h', b'u', b'b',
37
    ///     0x03, b'c', b'o', b'm',
38
    ///     0x00,       // End of QNAME
39
    ///     0x00, 0x01, // QTYPE = A
40
    ///     0x00, 0x01, // QCLASS = IN
41
    /// ];
42
    ///
43
    /// let packet = DNSPacket::from_bytes(&raw_packet).unwrap();
44
    ///
45
    /// assert_eq!(packet.header.qdcount, 1);
46
    /// assert_eq!(packet.questions[0].q_name, "github.com");
47
    /// assert!(packet.answers.is_empty());
48
    /// ```
49
2
    pub fn from_bytes(buf: &[u8]) -> Result<DNSPacket, SCloudException> {
50
2
        let mut pos = 0;
51
52
2
        let header = Header::from_bytes(&buf[pos..])
?0
;
53
2
        pos += Header::DNS_HEADER_LEN;
54
55
2
        let mut questions = Vec::new();
56
2
        for _ in 0..header.qdcount {
57
2
            let (q, consumed) = QuestionSection::from_bytes(&buf, pos)
?0
;
58
2
            pos += consumed;
59
2
            questions.push(q);
60
        }
61
62
2
        let mut answers = Vec::new();
63
2
        for _ in 0..header.ancount {
64
1
            let (ans, consumed) = AnswerSection::from_bytes(&buf, pos)
?0
;
65
1
            pos += consumed;
66
1
            answers.push(ans);
67
        }
68
69
2
        let mut authorities = Vec::new();
70
2
        for _ in 0..header.nscount {
71
2
            let (ns, consumed) = AuthoritySection::from_bytes(buf, pos)
?0
;
72
2
            pos += consumed;
73
2
            authorities.push(ns);
74
        }
75
76
2
        let mut additionals = Vec::new();
77
2
        for _ in 0..header.arcount {
78
1
            let (add, consumed) = AdditionalSection::from_bytes(&buf, pos)
?0
;
79
1
            pos += consumed;
80
1
            additionals.push(add);
81
        }
82
83
2
        Ok(DNSPacket {
84
2
            header,
85
2
            questions,
86
2
            answers,
87
2
            authorities,
88
2
            additionals,
89
2
        })
90
2
    }
91
92
    /// Serialize the DNS packet into a byte array
93
    /// # Exemple :
94
    /// ```
95
    /// let packet = DNSPacket::new_query(&[QuestionSection {
96
    ///     q_name: "github.com".to_string(),
97
    ///     q_type: DNSRecordType::A,
98
    ///     q_class: DNSClass::IN,
99
    /// }]);
100
    ///
101
    /// let bytes = packet.to_bytes().unwrap();
102
    ///
103
    /// // A valid DNS packet is at least 12 bytes (header)
104
    /// assert!(bytes.len() >= 12);
105
    /// ```
106
2
    pub fn to_bytes(&self) -> Result<Vec<u8>, SCloudException> {
107
2
        let mut bytes = Vec::new();
108
109
2
        if let Err(_) = self.header.to_bytes() {
110
0
            return Err(SCloudException::SCLOUD_HEADER_BYTES_EMPTY);
111
2
        }
112
2
        bytes.extend_from_slice(&self.header.to_bytes()
?0
);
113
114
2
        for q in &self.questions {
115
2
            bytes.extend_from_slice(&q.to_bytes()
?0
);
116
        }
117
118
2
        for 
ans1
in &self.answers {
119
1
            bytes.extend_from_slice(&ans.to_bytes()
?0
);
120
        }
121
122
2
        for 
auth1
in &self.authorities {
123
1
            bytes.extend_from_slice(&auth.to_bytes()
?0
);
124
        }
125
126
2
        for 
add1
in &self.additionals {
127
1
            bytes.extend_from_slice(&add.to_bytes()
?0
);
128
        }
129
130
2
        Ok(bytes)
131
2
    }
132
133
    /// Receive one or more `QuestionSection`, and return a new DNSPacket
134
    /// # Exemple :
135
    /// ```
136
    /// let query = DNSPacket::new_query(&[QuestionSection {
137
    ///                 q_name: "github.com".to_string(),
138
    ///                 q_type: DNSRecordType::A,
139
    ///                 q_class: DNSClass::IN,
140
    ///             }])
141
    /// ```
142
    ///
143
    /// # Return :
144
    /// ```
145
    /// DNSPacket {
146
    ///             header: Header {
147
    ///                 id: {random_generated_id},
148
    ///                 qr: false,
149
    ///                 opcode: 0,
150
    ///                 aa: false,
151
    ///                 tc: false,
152
    ///                 rd: true,
153
    ///                 ra: false,
154
    ///                 z: 0,
155
    ///                 rcode: 0,
156
    ///                 qdcount: 1,
157
    ///                 ancount: 0,
158
    ///                 nscount: 0,
159
    ///                 arcount: 0,
160
    ///             },
161
    ///             questions: vec![QuestionSection {
162
    ///                 q_name: "github.com".to_string(),
163
    ///                 q_type: DNSRecordType::A,
164
    ///                 q_class: DNSClass::IN,
165
    ///             }],
166
    ///             answers: vec![],
167
    ///             authorities: vec![],
168
    ///             additionals: vec![],
169
    ///         };
170
    /// ```
171
5
    pub fn new_query(question_section: &[QuestionSection]) -> DNSPacket {
172
5
        DNSPacket {
173
5
            header: Header {
174
5
                id: random::<u16>(),
175
5
                qr: false,
176
5
                opcode: 0,
177
5
                aa: false,
178
5
                tc: false,
179
5
                rd: true,
180
5
                ra: false,
181
5
                z: 0,
182
5
                rcode: 0,
183
5
                qdcount: question_section.len() as u16,
184
5
                ancount: 0,
185
5
                nscount: 0,
186
5
                arcount: 0,
187
5
            },
188
5
            questions: question_section.to_vec(),
189
5
            answers: vec![],
190
5
            authorities: vec![],
191
5
            additionals: vec![],
192
5
        }
193
5
    }
194
}